﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace IoC4Fun
{
    public class Container4Fun
    {
        private readonly IList<TypeRegister> _types = new List<TypeRegister>();

        /// <summary>
        /// Register an object in a IoC Container
        /// </summary>
        /// <typeparam name="TContract">Represent an interface</typeparam>
        /// <typeparam name="TImplementation">Representa the implementation of the contract</typeparam>
        public void Register<TContract, TImplementation>()
        {
            var typeFound = from t in _types
                            where t.Key == typeof(TContract) && t.Implementation == typeof(TImplementation)
                            select t;
            if (typeFound.Count() == 0)
                _types.Add(new TypeRegister { Key = typeof(TContract), Implementation = typeof(TImplementation) });

        }
        /// <summary>
        /// Register an object in a IoC Container
        /// </summary>
        /// <typeparam name="TContract">Represent an interface</typeparam>
        /// <typeparam name="TImplementation">Representa the implementation of the contract</typeparam>
        /// <typeparam name="name">Name of the property will inject the implementation</typeparam>
        public void Register<TContract, TImplementation>(string name)
        {
            var typeFound = from t in _types
                            where t.Key == typeof(TContract) && t.Implementation == typeof(TImplementation) && t.Name == name
                            select t;
            if (typeFound.Count() == 0)
                _types.Add(new TypeRegister { Key = typeof(TContract), Implementation = typeof(TImplementation), Name = name });

        }
        /// <summary>
        /// Register an object in a IoC Container
        /// </summary>
        /// <typeparam name="TContract">Represent an interface</typeparam>
        /// <typeparam name="TImplementation">Representa the implementation of the contract</typeparam>
        /// <typeparam name="name">Name of the property will inject the implementation</typeparam>
        /// <param name="instance">Set an instance of an implementation Already created</param>
        public void Register<TContract, TImplementation>(string name, TImplementation instance)
        {
            Register<TContract, TImplementation>(name, instance, PropertiesTypeCondition.DisposeAll);
        }
        /// <summary>
        /// Register an object in a IoC Container
        /// </summary>
        /// <typeparam name="TContract">Represent an interface</typeparam>
        /// <typeparam name="TImplementation">Representa the implementation of the contract</typeparam>
        /// <typeparam name="name">Name of the property will inject the implementation</typeparam>
        /// <param name="instance">Set an instance of an implementation Already created</param>
        /// <param name="PropertiesCondition">Does not check any properties dependency</param>
        public void Register<TContract, TImplementation>(string name, TImplementation instance, PropertiesTypeCondition propertiesCondition)
        {
            var typeFound = from t in _types
                            where t.Key == typeof(TContract) && t.Implementation == typeof(TImplementation)
                            select t;
            if (typeFound.Count() == 0)
                _types.Add(new TypeRegister { Key = typeof(TContract), Implementation = typeof(TImplementation), Name = name, Instance = instance, PropertiesCondition = propertiesCondition });
        }
        /// <summary>
        /// Register an object in a IoC Container
        /// </summary>
        /// <typeparam name="TContract">Represent an interface</typeparam>
        /// <typeparam name="TImplementation">Representa the implementation of the contract</typeparam>
        /// <typeparam name="name">Name of the property will inject the implementation</typeparam>
        /// <param name="instance">Set an instance of an implementation Already created</param>
        /// <param name="PropertiesCondition">Does not check any properties dependency</param>
        /// <param name="instanceCondition">Share = Use the same instance, NonShare = Create a new Instance every time</param>
        public void Register<TContract, TImplementation>(string name, TImplementation instance, PropertiesTypeCondition propertiesCondition, InstanceTypeCondition instanceCondition)
        {
            var typeFound = from t in _types
                            where t.Key == typeof(TContract) && t.Implementation == typeof(TImplementation)
                            select t;
            if (typeFound.Count() == 0)
                _types.Add(new TypeRegister { Key = typeof(TContract), Implementation = typeof(TImplementation), Name = name, Instance = instance, PropertiesCondition = propertiesCondition, InstanceCondition = instanceCondition });
        }

        public T Resolve<T>()
        {
            return (T)Resolve(typeof(T));
        }
        public object Resolve(Type contract)
        {
            return Resolve(contract, null);
        }
        private object Resolve(Type contract, string name)
        {
            return Resolve(contract, name, PropertiesTypeCondition.Search);
        }
        private object Resolve(Type contract, string name, PropertiesTypeCondition propertiesCondition)
        {
            object newInstance = null;
            Type implementation = null;
            TypeRegister typeRegistered = null;
            var typesFound = from t in _types
                             where t.Key == contract
                             select t;
            int totalTypesFound = typesFound.Count();
            if (totalTypesFound == 0)
            {
                if (propertiesCondition != PropertiesTypeCondition.AllowNull)
                    throw new Exception(string.Format("Type {0} is not registered ", contract.Name));
            }
            else if (totalTypesFound == 1)
                typeRegistered = typesFound.First();
            else
            {
                var typeChosen = from t in typesFound
                                 where t.Name == name
                                 select t;
                if (typeChosen.Count() == 0)
                {
                    var typeNotNamed = from t in typesFound
                                       where string.IsNullOrEmpty(t.Name)
                                       select t;
                    typeRegistered = typeNotNamed.First();
                }
                else
                    typeRegistered = typeChosen.First();
            }
            if (typeRegistered != null)
            {
                implementation = typeRegistered.Implementation;

                ConstructorInfo constructor = implementation.GetConstructors()[0];
                ParameterInfo[] constructorParameters = constructor.GetParameters();
                PropertyInfo[] propertiesParameters = implementation.GetProperties();
                if (constructorParameters.Length == 0 && propertiesParameters.Length == 0)
                {
                    if (typeRegistered.Instance == null && typeRegistered.InstanceCondition == InstanceTypeCondition.Share)
                        typeRegistered.Instance = Activator.CreateInstance(implementation);
                    return typeRegistered.Instance ?? Activator.CreateInstance(implementation);
                }

                List<object> parameters = new List<object>(constructorParameters.Length);
                foreach (ParameterInfo parameterInfo in constructorParameters)
                    parameters.Add(Resolve(parameterInfo.ParameterType));

                if (typeRegistered.Instance == null && typeRegistered.InstanceCondition == InstanceTypeCondition.Share)
                    typeRegistered.Instance = constructor.Invoke(parameters.ToArray());

                newInstance = typeRegistered.Instance ?? constructor.Invoke(parameters.ToArray());
                if (typeRegistered.PropertiesCondition != PropertiesTypeCondition.DisposeAll)
                    foreach (PropertyInfo propertyInfo in propertiesParameters)
                        if (propertyInfo.PropertyType.IsInterface)
                        {
                            object value = Resolve(propertyInfo.PropertyType, propertyInfo.Name, propertiesCondition);
                            if (value != null)
                                propertyInfo.SetValue(newInstance, value, null);
                        }
            }
            return newInstance;
        }
    }
}
